package com.cfpamf.ms.insur.pay.service;

import com.alibaba.fastjson.JSON;
import com.cfpamf.common.ms.exception.MSBizNormalException;
import com.cfpamf.common.ms.exception.MSException;
import com.cfpamf.common.ms.util.DateUtil;
import com.cfpamf.ms.insur.pay.constant.BizExceptEnum;
import com.cfpamf.ms.insur.pay.constant.BuzTypeEnum;
import com.cfpamf.ms.insur.pay.dao.PayOrderMapper;
import com.cfpamf.ms.insur.pay.dao.PayOrderTrxMapper;
import com.cfpamf.ms.insur.pay.dao.PayRefundMapper;
import com.cfpamf.ms.insur.pay.facade.constant.PayStatusEnum;
import com.cfpamf.ms.insur.pay.facade.dto.PayOrderDTO;
import com.cfpamf.ms.insur.pay.facade.dto.PreCreateOrderDTO;
import com.cfpamf.ms.insur.pay.facade.dto.QueryOrderDTO;
import com.cfpamf.ms.insur.pay.facade.dto.RefundOrderDTO;
import com.cfpamf.ms.insur.pay.facade.vo.PayOrderVO;
import com.cfpamf.ms.insur.pay.facade.vo.PreCreateOrderVO;
import com.cfpamf.ms.insur.pay.facade.vo.QueryOrderVO;
import com.cfpamf.ms.insur.pay.facade.vo.RefundOrderVO;
import com.cfpamf.ms.insur.pay.integration.dto.*;
import com.cfpamf.ms.insur.pay.pojo.po.PayOrderPO;
import com.cfpamf.ms.insur.pay.pojo.po.PayOrderTrxPO;
import com.cfpamf.ms.insur.pay.pojo.po.PayRefundPO;
import com.cfpamf.ms.insur.pay.util.InsurUtil;
import com.cfpamf.ms.lock.annotation.EnableLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

import static com.cfpamf.ms.insur.pay.pojo.convert.PayOrderConvert.CONVERT;

/**
 * 保险支付service
 */
@Slf4j
@Service
public class InsurPayService {

    @Autowired
    private BuildTrxService buildTrxService;

    @Autowired
    private PayOrderMapper payOrderMapper;

    @Autowired
    private PayOrderTrxMapper payOrderTrxMapper;

    @Autowired
    private PayRefundMapper payRefundMapper;
    @Autowired
    private PayExcpService payExcpService;

    /**
     * 预下单
     *
     * @param param
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public PreCreateOrderVO preCreateOrder(PreCreateOrderDTO param) {
        // 支付成功后不能再次支付
        PayOrderPO payOrder = queryPayOrderFromDB(param.getSourceSys(), param.getSourceOrderId(), null);
        if (payOrder != null && payOrder.getPayStatus() > PayStatusEnum.TO_PAID.getStatusCode()) {
            throw new MSBizNormalException(BizExceptEnum.PAY_REPERT);
        }

        // 之前存在支付流水支付方式没有变更,并且未过期，直接就返回以前串
        if (payOrder != null && payOrder.getPayStatus() == PayStatusEnum.TO_PAID.getStatusCode()
                && payOrder.getPayType().equals(param.getPayType())
                && payOrder.getPayMethod().equals(param.getPayMethod())) {
            PayOrderTrxPO payOrderTrx = queryPayOrderTrxFromDB(payOrder.getPayTrxNo());
            if (DateUtil.getNow().before(payOrderTrx.getExpireTime())) {
                return createPreOrderVO(payOrderTrx.getPrePayId(), param.getSourceOrderId(), payOrder.getOrderNo(), payOrder.getPayType(), payOrder.getPayMethod());
            }
        }

        // 生成新支付流水号
        String payTrxNo = buildTrxService.buildPayTrxNo();
        // 订单存在更新订单信息，否则创建新的订单
        if (payOrder == null) {
            payOrder = insertPayOrder(param, payTrxNo);
        } else {
            updatePayOrderPreCreateOrderInfo(payOrder.getId(), payTrxNo, param);
        }

        // 调用第三方支付接口返回支付需要签名
        BankPreCreateOrderParamDTO bankParam = CONVERT.preCreateOrderDTO2BankPreCreateOrderParamDTO(param);
        bankParam.setPayTrxNo(payTrxNo);
        BankPreCreateOrderRetDTO bankRet = InsurUtil.getBankPayService(param.getPayType()).bankPreCreateOrder(bankParam);
        //  创建交易流水
        insertPayOrderTrx(param, bankRet.getBody(), payTrxNo);

        return createPreOrderVO(bankRet.getBody(), param.getSourceOrderId(), payOrder.getOrderNo(), payOrder.getPayType(), payOrder.getPayMethod());
    }


    /**
     * 订单支付
     *
     * @param paramDTO
     * @return
     */
    public PayOrderVO payOrder(PayOrderDTO paramDTO) {
        return null;
    }

    /**
     * 订单查询
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public QueryOrderVO queryOrderInfo(QueryOrderDTO params) {
        // 查询数据库订单状态
        PayOrderPO payOrder = queryPayOrderFromDB(params.getSourceSys(), params.getSourceOrderId(), params.getOrderNo());
        // 订单未支付
        if (payOrder.getPayStatus() == PayStatusEnum.TO_PAID.getStatusCode()) {
            // 调用第三方同步最新订单支付信息
            syncPayOrderPayInfoWithBank(payOrder.getSourceSys(), payOrder.getPayTrxNo(), payOrder.getPayType());
            // 重新查询数据库订单状态
            payOrder = queryPayOrderFromDB(params.getSourceSys(), params.getSourceOrderId(), params.getOrderNo());
        } // 订单退款中
        else if (payOrder.getPayStatus() == PayStatusEnum.REFUNDING.getStatusCode()) {
            // 调用第三方同步最新订单退款信息
            syncPayRefundInfoWithBank(payOrder.getSourceSys(), payOrder.getOrderNo(), payOrder.getPayType());
            // 重新查询数据库退款状态
            payOrder = queryPayOrderFromDB(params.getSourceSys(), params.getSourceOrderId(), params.getOrderNo());
        }
        return CONVERT.payOrder2QueryOrderRetDTO(payOrder);
    }

    /**
     * 订单退款
     *
     * @param params
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @EnableLock(name = "insurance:pay:refundOrder", keys = {"#params.sourceSys + '-' + #params.sourceOrderId"})
    public RefundOrderVO refundOrder(RefundOrderDTO params) {
        PayOrderPO payOrder = queryPayOrderFromDB(params.getSourceSys(), params.getSourceOrderId(), params.getOrderNo());
        // 校验订单有效
        if (payOrder == null) {
            throw new MSBizNormalException(BizExceptEnum.ORDER_ERROR);
        }
        // 校验退款金额大于支付金额
        if (payOrder.getRefundAmount().add(params.getRefundAmount()).compareTo(payOrder.getOrderAmount()) > 0) {
            throw new MSBizNormalException(BizExceptEnum.REFUND_AMOUNT_OVER_PAY);
        }

        // 未支付
        if (payOrder.getPayStatus() == PayStatusEnum.TO_PAID.getStatusCode()) {
            throw new MSBizNormalException(BizExceptEnum.REFUND_UNPAY);
        }
        // 已支付
        else if (payOrder.getPayStatus() == PayStatusEnum.PAY_SUCCESS.getStatusCode()) {
            String refundTrxNo = buildTrxService.buildRefundTrxNo();

            InsurUtil.getBankPayService(payOrder.getPayType()).bankRefundOrder(buildBankRefundOrderParamDTO(payOrder, params, refundTrxNo));

            updatePayOrderRefundIngInfo(payOrder.getOrderNo(), params);

            insertPayRefund(payOrder, params, refundTrxNo);

            RefundOrderVO refundVO = new RefundOrderVO();
            refundVO.setSourceSys(params.getSourceSys());
            refundVO.setSourceOrderId(params.getSourceOrderId());
            refundVO.setRefundTrxNo(refundTrxNo);
            return refundVO;
        }
        // 退款中
        else if (payOrder.getPayStatus() == PayStatusEnum.REFUNDING.getStatusCode()) {
            throw new MSBizNormalException(BizExceptEnum.REFUND_ING);
        }
        // 退款成功
        else if (payOrder.getPayStatus() == PayStatusEnum.REFUND_SUCCESS.getStatusCode()) {
            throw new MSBizNormalException(BizExceptEnum.REFUND_REPEART);
        }
        // 其他状态
        else {
            throw new MSBizNormalException(BizExceptEnum.REFUND_ERROR);
        }
    }

    /**
     * 与第三方支付公司同步支付信息和状态
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void syncPayOrderPayInfoWithBank(String sourceSys, String payTrxNo, String payType) {
        BankQueryOrderParamDTO bankParam = new BankQueryOrderParamDTO();
        bankParam.setSourceSys(sourceSys);
        bankParam.setPayTrxNo(payTrxNo);
        BankQueryOrderRetDTO bankRet = InsurUtil.getBankPayService(payType).bankQueryOrderInfo(bankParam);

        if (bankRet.getPayStatus() == PayStatusEnum.PAY_SUCCESS.getStatusCode()) {
            PayOrderTrxPO dbPayOrderTrx = queryPayOrderTrxFromDB(payTrxNo);
            if (!Objects.equals(bankRet.getOrderAmount(), dbPayOrderTrx.getOrderAmount())) {
                log.error("银行订单支付金额错误 payTrxNo={},bankQueryOrderInfo={}", payTrxNo, JSON.toJSONString(bankRet));
                throw new MSException(BizExceptEnum.PAY_AMOUNT_ERROR);
            }
            // 更新交易流水
            PayOrderTrxPO updatePayOrderTrx = CONVERT.bankQueryOrderRetDTO2PayOrderTrxPO(bankRet);
            payOrderTrxMapper.updatePayOrderTrxBankPayInfo(payTrxNo, updatePayOrderTrx);

            PayOrderPO dbPayOrder = queryPayOrderFromDB(null, null, dbPayOrderTrx.getOrderNo());
            if (dbPayOrder.getPayStatus() == PayStatusEnum.PAY_SUCCESS.getStatusCode()
                    && !Objects.equals(dbPayOrder.getPayTrxNo(), payTrxNo)) {
                log.error("订单号{}存在重复多次重复支付", dbPayOrderTrx.getOrderNo());

                payExcpService.addPayExcpRecord(payTrxNo, BuzTypeEnum.PAY_CALLBACK.getCode(),null,"订单重复支付");
                return;
                //todo 如果支付方式变了并且未过期就关单；关单后往下走，过期就不管了
                //todo 或者 不关单，直接在回调时进异常表
            }

            // 更新订单信息(取流水订单号关联)
            PayOrderPO updatePayOrder = CONVERT.bankQueryOrderRetDTO2PayOrderPO(bankRet);
            updatePayOrder.setPayTrxNo(dbPayOrderTrx.getPayTrxNo());
            updatePayOrder.setPayType(dbPayOrderTrx.getPayType());
            updatePayOrder.setPayMethod(dbPayOrderTrx.getPayMethod());
            payOrderMapper.updatePayOrderBankPayInfo(dbPayOrderTrx.getOrderNo(), updatePayOrder);
        }
    }

    /**
     * 与第三方支付公司同步退款信息和状态
     */
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void syncPayRefundInfoWithBank(String sourceSys, String orderNo, String payType) {
        PayRefundPO queryCnt = new PayRefundPO();
        queryCnt.setOrderNo(orderNo);
        queryCnt.setRefundStatus(PayStatusEnum.REFUNDING.getStatusCode());
        PayRefundPO dbPayRefund = payRefundMapper.selectOne(queryCnt);
        if (dbPayRefund == null || dbPayRefund.getRefundStatus() != PayStatusEnum.REFUNDING.getStatusCode()) {
            log.warn("订单{}退款异常", orderNo);
            return;
        }

        BankQueryRefundParamDTO bankParam = new BankQueryRefundParamDTO();
        bankParam.setSourceSys(sourceSys);
        bankParam.setPayTrxNo(dbPayRefund.getPayTrxNo());
        bankParam.setRefundTrxNo(dbPayRefund.getRefundTrxNo());
        BankQueryRefundRetDTO bankRet = InsurUtil.getBankPayService(payType).bankQueryRefund(bankParam);

        if (bankRet.getRefundStatus() > PayStatusEnum.REFUNDING.getStatusCode()) {
            if (!Objects.equals(bankRet.getRefundAmount(), dbPayRefund.getRefundAmount())) {
                log.error("银行订单退款金额不一样 refundTrxNo={},bankQueryOrderInfo={}", dbPayRefund.getRefundTrxNo(), JSON.toJSONString(bankRet));
                throw new MSException(BizExceptEnum.REFUND_AMOUNT_ERROR);
            }

            PayOrderPO updatePayOrder = new PayOrderPO();
            updatePayOrder.setPayStatus(bankRet.getRefundStatus());
            payOrderMapper.updatePayOrderBankRefundInfo(dbPayRefund.getOrderNo(), updatePayOrder);

            PayRefundPO updatePayRefund = CONVERT.bankQueryRefundRetDTO2PayRefundPO(bankRet);
            payRefundMapper.updatePayRefundBankRefundInfo(dbPayRefund.getRefundTrxNo(), updatePayRefund);
        }
    }

    /**
     * 预下单接口返回对象
     *
     * @param body
     * @param sourceOrderId
     * @param orderNo
     * @param payType
     * @param payMethod
     * @return
     */
    private PreCreateOrderVO createPreOrderVO(String body, String sourceOrderId, String orderNo, String payType, String payMethod) {
        PreCreateOrderVO createOrderVO = new PreCreateOrderVO();
        createOrderVO.setBody(body);
        createOrderVO.setSourceOrderId(sourceOrderId);
        createOrderVO.setOrderNo(orderNo);
        createOrderVO.setPayType(payType);
        createOrderVO.setPayMethod(payMethod);
        return createOrderVO;
    }

    /**
     * 查询数据库订单信息
     *
     * @param sourceSys
     * @param sourceOrderId
     * @param orderNo
     * @return
     */
    private PayOrderPO queryPayOrderFromDB(String sourceSys, String sourceOrderId, String orderNo) {
        PayOrderPO queryCnt = new PayOrderPO();
        queryCnt.setSourceSys(sourceSys);
        queryCnt.setSourceOrderId(sourceOrderId);
        queryCnt.setOrderNo(orderNo);
        return payOrderMapper.selectOne(queryCnt);
    }

    /**
     * 新建订单信息
     *
     * @param paramDTO
     * @param payTrxNo
     * @return
     */
    private PayOrderPO insertPayOrder(PreCreateOrderDTO paramDTO, String payTrxNo) {
        PayOrderPO entity = CONVERT.preCreateOrder2PayOrder(paramDTO);
        entity.setOrderNo(buildOrderNo(paramDTO));
        entity.setPayStatus(PayStatusEnum.TO_PAID.getStatusCode());
        entity.setPayTrxNo(payTrxNo);
        payOrderMapper.insertSelective(entity);
        return entity;
    }

    /**
     * 新建订单流水
     *
     * @param paramDTO
     * @param prePayId
     * @param payTrxNo
     * @return
     */
    private PayOrderTrxPO insertPayOrderTrx(PreCreateOrderDTO paramDTO, String prePayId, String payTrxNo) {
        PayOrderTrxPO entity = CONVERT.preCreateOrder2PayOrderTrx(paramDTO);
        entity.setPrePayId(prePayId);
        entity.setPayTrxNo(payTrxNo);
        entity.setExpireTime(DateUtil.add(paramDTO.getOrderTime(), paramDTO.getOrderPeriod(), Calendar.MINUTE));
        entity.setOrderNo(buildOrderNo(paramDTO));
        entity.setPayStatus(PayStatusEnum.TO_PAID.getStatusCode());
        payOrderTrxMapper.insertSelective(entity);
        return entity;
    }

    /**
     * 新建订单退款
     *
     * @param payOrder
     * @param params
     * @return
     */
    private void insertPayRefund(PayOrderPO payOrder, RefundOrderDTO params, String refundTrxNo) {
        PayRefundPO entity = new PayRefundPO();
        entity.setRefundTrxNo(refundTrxNo);
        entity.setOrderNo(payOrder.getOrderNo());
        entity.setPayTrxNo(payOrder.getPayTrxNo());
        entity.setBankPayTrxNo(payOrder.getBankPayTrxNo());
        entity.setOrderAmount(payOrder.getOrderAmount());
        entity.setPayType(payOrder.getPayType());
        entity.setRefundAmount(params.getRefundAmount());
        entity.setRefundReason(params.getRefundReason());
        entity.setRefundRequestTime(new Date());
        entity.setRefundStatus(PayStatusEnum.REFUNDING.getStatusCode());
        entity.setSourceSys(params.getSourceSys());
        payRefundMapper.insertSelective(entity);
    }

    /**
     * 更新订单预支付信息
     *
     * @param id
     * @param payTrxNo
     * @param paramDTO
     */
    private void updatePayOrderPreCreateOrderInfo(Long id, String payTrxNo, PreCreateOrderDTO paramDTO) {
        PayOrderPO updatePayOrder = CONVERT.preCreateOrder2PayOrder(paramDTO);
        updatePayOrder.setPayTrxNo(payTrxNo);
        payOrderMapper.updatePayOrderPreCreateOrderInfo(id, updatePayOrder);
    }

    /**
     * 更新订单退款信息
     *
     * @param orderNo
     * @param params
     */
    private void updatePayOrderRefundIngInfo(String orderNo, RefundOrderDTO params) {
        PayOrderPO entity = new PayOrderPO();
        entity.setRefundAmount(params.getRefundAmount());
        entity.setPayStatus(PayStatusEnum.REFUNDING.getStatusCode());
        payOrderMapper.updatePayOrderBankRefundInfo(orderNo, entity);
    }

    /**
     * 查询订单流水
     *
     * @param payTrxNo
     * @return
     */
    private PayOrderTrxPO queryPayOrderTrxFromDB(String payTrxNo) {
        PayOrderTrxPO queryCnt = new PayOrderTrxPO();
        queryCnt.setPayTrxNo(payTrxNo);
        return payOrderTrxMapper.selectOne(queryCnt);
    }

    /**
     * 构建银行退款参数
     *
     * @param payOrder
     * @param params
     * @param refundTrxNo
     * @return
     */
    private BankRefundOrderParamDTO buildBankRefundOrderParamDTO(PayOrderPO payOrder, RefundOrderDTO params, String refundTrxNo) {
        BankRefundOrderParamDTO bankParam = new BankRefundOrderParamDTO();
        bankParam.setSourceSys(payOrder.getSourceSys());
        bankParam.setPayTrxNo(payOrder.getPayTrxNo());
        bankParam.setBankPayTrxNo(payOrder.getBankPayTrxNo());
        bankParam.setOrderAmount(payOrder.getOrderAmount());
        bankParam.setRefundAmount(params.getRefundAmount());
        bankParam.setRefundReason(params.getRefundReason());
        bankParam.setRefundTrxNo(refundTrxNo);
        return bankParam;
    }

    /**
     * @param paramDTO
     * @return
     */
    private String buildOrderNo(PreCreateOrderDTO paramDTO) {
        return paramDTO.getSourceSys().toUpperCase() + paramDTO.getSourceOrderId();
    }
}
